home *** CD-ROM | disk | FTP | other *** search
/ Technotools / Technotools (Chestnut CD-ROM)(1993).ISO / lang_pas / mtask11 / mtask.prt < prev    next >
Text File  |  1988-11-12  |  19KB  |  854 lines

  1.  
  2.  
  3. MTASK 1.1 by Wayne Conrad
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.                                 MTASK
  15.  
  16.  
  17. MTASK is a unit for Turbo Pascal 5.0, to allow a Turbo Pascal program to
  18. exhibit simple multi-tasking.  MTASK gives your program a
  19. non-preemptive, request driven multi-tasking capability.  I will explain
  20. what I mean by that later.
  21.  
  22. MTASK was written and donated to the public domain by Wayne E. Conrad
  23. (me) in November of 1988.  I may be contact via my BBS,
  24.  
  25.      Pascalaholics Anonymous
  26.      (602) 484-9356
  27.      300/1200/2400 bps
  28.      24 hours/day
  29.  
  30. or by mail at my home:
  31.  
  32.      2627 North 51st Ave, #219
  33.      Phoenix, AZ  85035
  34.  
  35. I am interested in any modifications, bug reports, or comments you have.
  36.  
  37. If you modify this package, please keep my name and the name of any
  38. other programmers who've worked on it intact.  Please distribute the
  39. complete ARC file, with documentation and demonstration programs
  40. included.
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.                                  -1-
  64.  
  65.  
  66.  
  67.  
  68.  
  69. MTASK 1.1 by Wayne Conrad
  70.  
  71.  
  72.  
  73. 1.1 INTRODUCTION
  74.  
  75.  
  76. MTASK allows your Turbo Pascal program to do simple multi-tasking.  I
  77. call MTASK's brand of multi-tasking "non-preemptive, request driven."
  78.  
  79. Preemptive is a fairly common phrase, meaning that the switch from one
  80. task to another can happen at almost any time.  Most preemptive systems
  81. have an interrupt driver hooked to a hardware timer, which causes a task
  82. switch every time the hardware timer goes off.  The advantage of this
  83. kind of multi-tasking is that your programs don't have to be written
  84. with multi-tasking in mind, and don't even have to know that its taking
  85. place.  Also, no program can hog the system for long, because the
  86. interrupt driver switches from one program to another fairly often.
  87. Desqview and Double-DOS are programs that do preemptive,
  88. interrupt-driven multi-tasking.  The disadvantage of this kind of
  89. multi-tasking is that it can be complex to write and difficult in the
  90. extreme to debug.  These difficulties are compounded in an MS-DOS
  91. environment because MS-DOS was not meant to be used in a multi-tasking
  92. environment.
  93.  
  94. On the other hand, non-preemptive multi-tasking only switches tasks at
  95. certain, well defined times.  There is no interrupt driver that forces
  96. task switches.  In the original Macintosh operating system, for example,
  97. task switches only occured when a task called the operating system.  The
  98. advantage of non-preemptive multi-tasking is that it is much simpler to
  99. write and debug than preemptive multi-tasking, because the system has
  100. total control of when task-switches occur.  The disadvantage to this
  101. form of multi-tasking is that a task must request a task switch often if
  102. the other tasks are to receive their chance to execute.  If a task does
  103. not request a task switch for a long time, the other tasks will appear
  104. to pause.  What's worse, if a task crashes, it won't be able to call the
  105. operating system to let the other tasks execute, so they'll all be hung
  106. too.
  107.  
  108. MTASK implements a very simple method of non-preemptive multi-tasking
  109. that I call "request driven."  Request driven means that task switches
  110. occur only when the current task calls MTASK and requests a switch.
  111. (The sole exception is that a task switch occurs when the current task
  112. terminates itself).  This is about the simplest form of multi-tasking
  113. that I can envision.  It is so simple that the entire MTASK unit
  114. compiles to only about 1400 bytes with stack checking and range checking
  115. turned off, or less if you don't use all of its procedures.  This
  116. simplicity also made MTASK easy to write and debug.  MTASK was written
  117. and debugged in one day!
  118.  
  119.  
  120.  
  121. 1.2 WHAT ARE MTASK'S LIMITS?
  122.  
  123.  
  124. MTASK allows your program to set up multiple tasks within itself.  These
  125. tasks will execute concurrently.  However, it does not effect anything
  126.  
  127.  
  128.  
  129.                                  -2-
  130.  
  131.  
  132.  
  133.  
  134.  
  135. MTASK 1.1 by Wayne Conrad
  136.  
  137.  
  138.  
  139. outside of your program.  It does not allow you to run multiple
  140. programs, multiple copies of COMMAND.COM, or anything else like that.
  141. It simply allows your program to do several things concurrently without
  142. stumbling over itself.
  143.  
  144. As far as DOS is concerned, your program using MTASK is still just a
  145. simple program.  All of the gymnastics to keep track of multiple tasks
  146. are done by MTASK, withing your program, without the knowledge or
  147. consent of DOS or anything else outside of your program.  Because MTASK
  148. is so simple, it will coexist fine with any "real" multi-tasking DOS you
  149. have set up, such as DesqView or Double-DOS.  Whenever the DOS gives
  150. your program some time, your program will dole out that time to its
  151. tasks.
  152.  
  153. Your program must continue to execute for its tasks to execute.  If any
  154. task in your program exits to DOS for any reason, including a run-time
  155. error, all tasks stop executing.  If one of your tasks shells out by
  156. using Turbo's Exec function, then the other tasks in your program are
  157. suspended until control returns from the Exec function to your program.
  158.  
  159. MTASK must not be made into an overlay.  Any of the tasks it controls
  160. may be overlays, although that may be unwise.  You could end up loading
  161. an overlay from disk during each task switch!
  162.  
  163.  
  164.  
  165.  
  166.  
  167.  
  168.  
  169.  
  170.  
  171.  
  172.  
  173.  
  174.  
  175.  
  176.  
  177.  
  178.  
  179.  
  180.  
  181.  
  182.  
  183.  
  184.  
  185.  
  186.  
  187.  
  188.  
  189.  
  190.  
  191.  
  192.  
  193.  
  194.  
  195.                                  -3-
  196.  
  197.  
  198.  
  199.  
  200.  
  201. MTASK 1.1 by Wayne Conrad
  202.  
  203.  
  204.  
  205. 2.1 SUMMARY OF PROCEDURES AND FUNCTIONS
  206.  
  207.  
  208. To use MTASK, include it in your program's USES statements.  MTASK will
  209. initialize itself automatically, making your main program task #1.  Your
  210. program can then use the following procedures and functions to create
  211. and control tasks:
  212.  
  213.  
  214.      create_task         Create another task
  215.  
  216.      terminate_task      Destroy a task
  217.  
  218.      switch_task         Switch to another task
  219.  
  220.      current_task_id     Return the task ID of the current task
  221.  
  222.      number_of_tasks     Return the current number of tasks
  223.  
  224.      get_task_info       Get information about all tasks
  225.  
  226.  
  227.  
  228.  
  229.  
  230.  
  231.  
  232.  
  233.  
  234.  
  235.  
  236.  
  237.  
  238.  
  239.  
  240.  
  241.  
  242.  
  243.  
  244.  
  245.  
  246.  
  247.  
  248.  
  249.  
  250.  
  251.  
  252.  
  253.  
  254.  
  255.  
  256.  
  257.  
  258.  
  259.  
  260.  
  261.                                  -4-
  262.  
  263.  
  264.  
  265.  
  266.  
  267. MTASK 1.1 by Wayne Conrad
  268.  
  269.  
  270.  
  271. 2.1.1 PROCEDURE CREATE_TASK
  272.  
  273.  
  274. PROCEDURE create_task
  275.   (
  276.   task      : task_proc;
  277.   VAR param ;
  278.   stack_size: Word;
  279.   VAR id    : Word;
  280.   VAR result: Word
  281.   );
  282.  
  283.  
  284.      TASK is the procedure to make into a task.  It must match type
  285.      task_proc, having a single variable as its parameter.
  286.  
  287.      PARAM is the parameter to pass to new_task.  It may be a variable of any
  288.      type, so long its what the task expects.  For example, if you pass a
  289.      Word and the task expects a LongInt, the task will get invalid data.
  290.  
  291.      STACK_SIZE is the size of the new task's stack.  A stack will be
  292.      allocated from the heap.  The minimum stack size is 512 bytes, because
  293.      Turbo's stack-check procedure flags a "stack overflow" error if less
  294.      than 512 bytes of stack remain.
  295.  
  296.      ID is set to the task ID of the newly created task.  If the task is not
  297.      created because of an error, then id is not set.
  298.  
  299.      RESULT is the result code, which is set to one of these values:
  300.  
  301.  
  302.           0                   No error, task created ok
  303.           heap_full           Unable to allocate heap for the task's
  304.                               stack
  305.           too_many_tasks      Maximum number of tasks are already
  306.                               running
  307.  
  308.  
  309. The new task is created and added to the end of the task list.  The new
  310. task will be executed when the task before it calls switch_task.
  311.  
  312.  
  313.  
  314.  
  315.  
  316.  
  317.  
  318.  
  319.  
  320.  
  321.  
  322.  
  323.  
  324.  
  325.  
  326.  
  327.                                  -5-
  328.  
  329.  
  330.  
  331.  
  332.  
  333. MTASK 1.1 by Wayne Conrad
  334.  
  335.  
  336.  
  337. 2.1.2 PROCEDURE TERMINATE_TASK
  338.  
  339.  
  340. PROCEDURE terminate_task (id: Word; VAR result: Word);
  341.  
  342.  
  343.      ID is the task id of the task you want to terminate.  If ID = 0,
  344.      then the current task will be terminated.
  345.  
  346.      RESULT is the result code, which is set to one of these values.
  347.  
  348.           0                   No error, task deleted ok
  349.           invalid_task_id     There is no task with that ID number
  350.  
  351.  
  352. The designated task will be removed from the task list.  If its stack
  353. was allocated from the heap, it is returned to the heap.
  354.  
  355. If the terminated task is the current task and there is another task in
  356. the task list, a task switch occurs.  On the other hand, if the
  357. terminated task is the current task and there are no other tasks in the
  358. task list, then the program exits to DOS.
  359.  
  360. A task may terminate itself by returning from its main procedure.  For
  361. example, when this task is executed, it will immediately display a
  362. message and then terminate itself.
  363.  
  364.  
  365.      PROCEDURE task (VAR param);
  366.      BEGIN
  367.        Writeln ('We just started, but already we're terminating');
  368.      END;
  369.  
  370.  
  371.  
  372.  
  373.  
  374.  
  375.  
  376.  
  377.  
  378.  
  379.  
  380.  
  381.  
  382.  
  383.  
  384.  
  385.  
  386.  
  387.  
  388.  
  389.  
  390.  
  391.  
  392.  
  393.                                  -6-
  394.  
  395.  
  396.  
  397.  
  398.  
  399. MTASK 1.1 by Wayne Conrad
  400.  
  401.  
  402.  
  403. 2.1.3 PROCEDURE switch_task
  404.  
  405.  
  406.      PROCEDURE switch_task;
  407.  
  408.  
  409. This procedure causes an immediate switch to the next task in the task
  410. list.  The task list is always scanned as a circular list.  For
  411. example, if there are three tasks in the list -- task 1, task 2, and
  412. task 3 -- then they will be executed in this order:
  413.  
  414.  
  415.      1, 2, 3, 1, 2, 3, 1, 2, 3 . . .
  416.  
  417.  
  418. If the current task is the only task, then no task switch occurs.
  419.  
  420. The stack pointer is switched to its position in the new task's stack.
  421. If the new task has just been created, then its main procedure will be
  422. executed from the beginning.  On the other hand, if the new task had put
  423. itself to sleep by asking for a task switch, then control will return to
  424. the point where it called switch_task.
  425.  
  426.  
  427.  
  428. 2.1.4 FUNCTION CURRENT_TASK_ID
  429.  
  430.  
  431.      FUNCTION current_task_id: task_id;
  432.  
  433.  
  434. This function returns the task ID number of the currently executing
  435. task.  When calling an MTASK procedure to do something to a task, the
  436. task ID number is always used to identify the task.
  437.  
  438. A task is assigned its ID number when it is created.  A task's ID number
  439. belongs to it as long as that task exists, and will not be changed or
  440. reassigned until the task terminates.
  441.  
  442.  
  443.  
  444. 2.1.5 FUNCTION NUMBER_OF_TASKS
  445.  
  446.  
  447.      FUNCTION number_of_tasks: task_number;
  448.  
  449.  
  450. This function returns the number of tasks in the task list.  There will
  451. always be at least one task.
  452.  
  453.  
  454.  
  455.  
  456.  
  457.  
  458.  
  459.                                  -7-
  460.  
  461.  
  462.  
  463.  
  464.  
  465. MTASK 1.1 by Wayne Conrad
  466.  
  467.  
  468.  
  469. 2.1A PROCEDURE get_task_info
  470.  
  471.  
  472.      PROCEDURE get_task_info
  473.        (
  474.        VAR info: task_info_array;
  475.        VAR n   : task_number
  476.        );
  477.  
  478.  
  479. INFO is an array of task information.  You receive a copy of the actual
  480. task information array, not the original.  See MTASK8S for the
  481. definition and description of task_info_array.
  482.  
  483. I really don't expect that I'll ever use this procedure, but it's there
  484. if your program ever needs to know what the current state of MTASK is.
  485.  
  486.  
  487.  
  488.  
  489.  
  490.  
  491.  
  492.  
  493.  
  494.  
  495.  
  496.  
  497.  
  498.  
  499.  
  500.  
  501.  
  502.  
  503.  
  504.  
  505.  
  506.  
  507.  
  508.  
  509.  
  510.  
  511.  
  512.  
  513.  
  514.  
  515.  
  516.  
  517.  
  518.  
  519.  
  520.  
  521.  
  522.  
  523.  
  524.  
  525.                                  -8-
  526.  
  527.  
  528.  
  529.  
  530.  
  531. MTASK 1.1 by Wayne Conrad
  532.  
  533.  
  534.  
  535. 3.1 TRICKS AND TRAPS
  536.  
  537.  
  538. This section focuses on some of the tricks and traps of programming in
  539. this multi-tasking environment.  Like all multi-tasking environments,
  540. strange things can happen.  You'll learn how to watch for problems with
  541. shared data, and crunched parameters.
  542.  
  543. I will only give a few examples of the problems that can occur in
  544. multi-tasking environments.  There are other problems that can occur
  545. when using MTASK, and many more problems that can occur when using a
  546. preemptive multi-tasker such as UNIX.  This section should help you to
  547. begin thinking like a real-time programmer, giving you an idea of the
  548. kinds of problems to watch for.  For a real education on concurrent
  549. programming, head to your library or book store and look for a book on
  550. operating systems.
  551.  
  552.  
  553.  
  554. 3.1.1 PASSING PARAMETERS TO TASKS
  555.  
  556.  
  557. When you create a task, you can pass a parameter to it.  For example, a
  558. BBS program needs to tell a task which node it is, so that the task
  559. knows which serial port to use for i/o.  The parameter you pass is
  560. "untyped," meaning that it can be any type of variable.  You must be
  561. familiar with how Turbo handles untyped variables.
  562.  
  563. The sample program TEST19S shows how to pass a word variable to a
  564. task.  You can pass any kind of variable, including records, arrays, and
  565. even files.
  566.  
  567. One thing to remember is that when you pass an untyped parameter to a
  568. task, you are actually passing the address of the parameter, not the
  569. parameter itself.  Therefore, if you pass the task a paramater and then
  570. modify the parameter, the task may see the new value instead of the old
  571. value.  It will all depend upon where task switches occur.
  572.  
  573. As a general rule, parameters you pass to a task should be global
  574. variables or typed constants.  Global variables and typed constants are
  575. both in the data segment.  Local variables are declared on the current
  576. task's stack, and cannot be assured of existing for very long.  If you
  577. a procedure passes one of its local variables to a task that it's
  578. creating, and then the procedure returns, that local variable is "thrown
  579. away" and its space can be reused by other procedures.  That would cause
  580. the value of the parameter you passed to the task to change
  581. unpredictably.
  582.  
  583.  
  584.  
  585.  
  586.  
  587.  
  588.  
  589.  
  590.  
  591.                                  -9-
  592.  
  593.  
  594.  
  595.  
  596.  
  597. MTASK 1.1 by Wayne Conrad
  598.  
  599.  
  600.  
  601. 3.1.2 SHARED DATA
  602.  
  603.  
  604. Problems can occur when two or more tasks are using the same global
  605. variables.  If two or more tasks have access to the same variable, you
  606. need to consider carefully what will happen if two tasks access the
  607. variable concurrently.  This pseudo-code example shows two tasks.
  608. task_a is computing the sum of an array of Reals.  Task_b is clearing
  609. the values in the array.
  610.  
  611.  
  612.  
  613.      CONST
  614.        data_size = 1000;
  615.      VAR
  616.        data: ARRAY [1.11/12/88ta_size] OF Real;
  617.  
  618.  
  619.      PROCEDURE task_a;
  620.         .
  621.         .
  622.         .
  623.      sum := 0.0;
  624.      FOR i := 1 TO data_size DO
  625.        BEGIN
  626.        sum := sum + data [i];
  627.        switch_task;
  628.        END;
  629.         .
  630.         .
  631.         .
  632.      END;
  633.  
  634.  
  635.      PROCEDURE task_b;
  636.         .
  637.         .
  638.         .
  639.      FOR i := data_size DOWNTO 1 DO
  640.        BEGIN
  641.        data [i] := 0.0;
  642.        switch_task;
  643.        END;
  644.         .
  645.         .
  646.         .
  647.      END;
  648.  
  649.  
  650. Do you see what happens if task_a is computing the average at the same
  651. time task_b is clearing the array?  The average will end up being
  652. incorrect, because the data being averaged is being changed while the
  653. average is being computed.  Obviously, this example is contrived.
  654.  
  655.  
  656.  
  657.                                  -10-
  658.  
  659.  
  660.  
  661.  
  662.  
  663. MTASK 1.1 by Wayne Conrad
  664.  
  665.  
  666.  
  667. Nobody in their right mind would call switch_task inside those
  668. loops.  That causes many more context switches than are necessary.
  669.  
  670. One way to avoid the problem in this particular example is not to call
  671. switch_task inside either of the loops.  Then you could be sure that an
  672. average would not take place while you were clearing the array, and
  673. array clearing would not take place during an average.
  674.  
  675. You cannot always avoid calling switch_task, however.  Suppose that
  676. floating point addition on your computer was so slow that it took many
  677. seconds to compute the average.  You may have other tasks that cannot
  678. afford to be denied CPU time for more than a fraction of a second.  What
  679. do you do?
  680.  
  681. The solution here is to create a flag that indicates when a task is
  682. using the data array.  When one task is using the data array the flag
  683. will be set to True, indicating that no other task should access it.
  684.  
  685.  
  686.      CONST
  687.        flag: Boolean = False;
  688.  
  689.  
  690.      PROCEDURE task_a;
  691.         .
  692.         .
  693.         .
  694.      WHILE flag DO
  695.        switch_task;
  696.      flag := True;
  697.      sum := 0.0;
  698.      FOR i := 1 TO data_size DO
  699.        BEGIN
  700.        sum := sum + data [i];
  701.        switch_task;
  702.        END;
  703.      flag := False;
  704.         .
  705.         .
  706.         .
  707.      END;
  708.  
  709.  
  710.  
  711.  
  712.  
  713.  
  714.  
  715.  
  716.  
  717.  
  718.  
  719.  
  720.  
  721.  
  722.  
  723.                                  -11-
  724.  
  725.  
  726.  
  727.  
  728.  
  729. MTASK 1.1 by Wayne Conrad
  730.  
  731.  
  732.  
  733.      PROCEDURE task_b;
  734.         .
  735.         .
  736.         .
  737.      WHILE flag DO
  738.        switch_task;
  739.      flag := True;
  740.      FOR i := data_size DOWNTO 1 DO
  741.        BEGIN
  742.        data [i] := 0.0;
  743.        switch_task;
  744.        END;
  745.      flag := True;
  746.         .
  747.         .
  748.         .
  749.      END;
  750.  
  751.  
  752. Do you see what's going on here?  Before task_a does an average, it
  753. checks the flag to see whether someone else is messing with the data
  754. array.  If someone is, then it waits until the data structure is
  755. available, sets the flag to indicate that it now "owns" the data array,
  756. and proceeds to compute the average.  When the average is finished,
  757. task_a resets the flag, to allow any other task which is waiting for the
  758. data array to have access.  task_b is doing exactly the same thing.
  759.  
  760. Now both tasks can go on calling switch_task even while messing with the
  761. data array, without concern that some other task will access the data
  762. array at the same time.  This technique will work for any number of
  763. tasks.
  764.  
  765.  
  766.  
  767. 3.1.3 WHEN TO SWITCH TASKS?
  768.  
  769.  
  770. Obviously, the examples in 3.1.2 switch tasks far too often.  The
  771. program will spend more time bouncing from one task to another than it
  772. will doing anything useful!  If your loop is too time consuming to leave
  773. out task switches, and switching tasks during every iteration of the
  774. loop is too often, try something like this:
  775.  
  776.  
  777.      FOR i := 1 TO 10000 DO
  778.        BEGIN
  779.        IF i MOD 100 = 0 THEN
  780.          switch_tasks;
  781.        do_something_useful;
  782.        END;
  783.  
  784.  
  785. This will switch tasks every hundreth iteration of the loop.
  786.  
  787.  
  788.  
  789.                                  -12-
  790.  
  791.  
  792.  
  793.  
  794.  
  795. MTASK 1.1 by Wayne Conrad
  796.  
  797.  
  798.  
  799. If your program is going to do something that takes a while, like disk
  800. i/o, it should probably switch tasks before doing so to let the other
  801. tasks get some time before the long delay occurs.  In fact, if you are
  802. doing several lengthy disk operations in a row, call switch_task before
  803. every one.
  804.  
  805.  
  806.      Assign (inf, 'INPUT11/12/88T');
  807.      switch_tasks;
  808.      Reset (inf);
  809.      Assign (outf, 'OUTPUT11/12/88T');
  810.      switch_tasks;
  811.      Rewrite (outf);
  812.  
  813.  
  814. Many programs have to wait for input at some point.  Input loops are a
  815. perfect place to switch tasks.  In fact, any time a task cannot proceed
  816. because its input is not ready, or for any other reason, it should
  817. switch tasks.
  818.  
  819.  
  820.      WHILE NOT KeyPressed DO
  821.        switch_tasks;
  822.      ch := ReadKey;
  823.  
  824.  
  825. It is a matter of judgement where task switches should occur.  It will
  826. depend upon the program and circumstances around each operation.
  827.  
  828.  
  829.  
  830. 4.1 REVISION HISTORY
  831.  
  832.  
  833. Version 1.0, MTASK10.ARC.  Original release by Wayne E. Conrad
  834.  
  835. Version 1.1, MTASK11.ARC.  Minor changes to documentation, including
  836. using spaces instead of tabs.  ARC file now includes the original
  837. documentation in Multi-Edit format, as well as the printable file.
  838.  
  839.  
  840.  
  841.  
  842.  
  843.  
  844.  
  845.  
  846.  
  847.  
  848.  
  849.  
  850.  
  851.  
  852.                                  -13-
  853.  
  854.